﻿#region Copyright
/* © 2008 George Tsiokos, licensed under the Microsoft Reciprocal License (Ms-RL) */
#endregion
using System;
using System.Collections.Generic;
using Comet.ComponentModel;

namespace Comet.Collections.Generic {
    /// <summary>
    /// Experimental: A list where items are removed based on a time interval. A unit of time is selected, and a capacity
    /// of how many of those unit of times are retained.
    /// </summary>
    /// <remarks>
    /// <para>Short timespans with low item utilization are less efficent than larger timespans with high item utilization.</para>
    /// <para>For example,
    /// A timespan of 1 hour, timespan capacity of 24, and initial item capacity of 2,000 means that the list will start out
    /// with a item capacity of 2 thousand per hour, for 24 utilized* hours. When an item is added to the
    /// list when 24 utilized hours exist, the oldest hour is removed.</para> 
    /// <para>Values are retained for the specified <see cref="TimeSpanCapacity"/> based on the specified <see cref="TimeSpan"/>, optionally with the specified initial <see cref="ItemCapacity"/>.</para>
    /// <para>* Unused units of time do not decrement the timespan capacity.</para>
    /// </remarks>
    /// <typeparam name="T"></typeparam>
    public sealed class TemporalList<T> : IList<T> {

        #region Fields (5)

        private long _currentTimestamp;
        private readonly int _itemCapacity;
        private readonly SortedList<long, List<T>> _timeSpans = new SortedList<long, List<T>> ();
        private readonly long _timespan;
        private readonly int _timespanCapacity;

        #endregion Fields

        #region Constructors (2)

        public TemporalList (TimeSpan timespan, int timespanCapacity, int itemCapacity) {
            _timespan = timespan.Ticks;
            _timespanCapacity = timespanCapacity;
            _itemCapacity = itemCapacity;
        }

        public TemporalList (TimeSpan timespan, int timespanCapacity)
            : this (timespan, timespanCapacity, int.MaxValue) {
        }

        #endregion Constructors

        #region Properties (7)

        public long Count {
            get {
                long count = 0;
                foreach (List<T> list in _timeSpans.Values)
                    count += list.Count;
                return count;
            }
        }

        public KeyValuePair<TemporalIndex, T> IndexAndValue {
            get {
                long timestamp = GetTimestamp ();
                int index = GetLastIndex (timestamp);
                return new KeyValuePair<TemporalIndex, T> (
                    new TemporalIndex (timestamp, index), _timeSpans[timestamp][0]);
            }
        }

        public bool IsReadOnly {
            get {
                return false;
            }
        }

        public int ItemCapacity {
            get {
                return _itemCapacity;
            }
        }

        public T this[TemporalIndex index] {
            get {
                return GetList (index.Timestamp)[index.Index];
            }
            set {
                GetList (index.Timestamp)[index.Index] = value;
            }
        }

        public TimeSpan TimeSpan {
            get {
                return TimeSpan.FromTicks (_timespan);
            }
        }

        public int TimespanCapacity {
            get {
                return _timespanCapacity;
            }
        }

        public int TimeSpansUtilized {
            get {
                return _timeSpans.Count;
            }
        }

        #endregion Properties

        #region Methods (18)


        // Public Methods (13) 

        public TemporalIndex Add (T item) {
            long hourTimestamp = GetTimestamp ();
            Shrink (hourTimestamp);
            List<T> items = GetList (hourTimestamp);
            //TODO: catch - 
            items.Add (item);
            return new TemporalIndex (hourTimestamp, GetLastIndex (items));
        }

        public TemporalIndex AddRange (IEnumerable<T> collection) {
            long hourTimestamp = GetTimestamp ();
            Shrink (hourTimestamp);
            List<T> items = GetList (hourTimestamp);
            items.AddRange (collection);
            return new TemporalIndex (hourTimestamp, GetLastIndex (items));
        }

        public void Clear () {
            _timeSpans.Clear ();
        }

        public bool Contains (T item) {
            foreach (List<T> list in _timeSpans.Values)
                foreach (T t in list)
                    if (object.Equals (item, t))
                        return true;
            return false;
        }

        public void CopyTo (T[] array, int arrayIndex) {
            throw new NotImplementedException ();
        }

        public IEnumerator<T> GetEnumerator () {
            foreach (List<T> list in _timeSpans.Values) {
                foreach (T item in list)
                    yield return item;
            }
        }

        public IEnumerable<T> GetFrom (MutableValueWrapper<TemporalIndex> index) {
            if (null == index)
                throw new ArgumentNullException ("index");

            long timestamp = index.Value.Timestamp;
            IList<long> hours = _timeSpans.Keys;
            int hoursIndex = 0;
            int hoursCount = hours.Count;
            while (hours[hoursIndex] < timestamp)
                hoursIndex++;
            if (hoursIndex >= hoursCount) {
                timestamp = GetTimestamp ();
                index = new TemporalIndex (timestamp, GetLastIndex (timestamp));
                yield break;
            }
            int startingIndex;
            if (hours[hoursIndex] == timestamp)
                startingIndex = index.Value.Index;
            else
                startingIndex = 0;

            int itemCount = 1;
            for (; hoursIndex < hoursCount; hoursIndex++) {
                List<T> items = _timeSpans[hours[hoursIndex]];
                itemCount = items.Count;
                for (; startingIndex < itemCount; startingIndex++)
                    yield return items[startingIndex];
                startingIndex = 0;
            }
            index = new TemporalIndex (timestamp, itemCount - 1);
        }

        public int GetLastIndex () {
            return GetLastIndex (GetTimestamp ());
        }

        public long GetTimestamp () {
            return DateTime.Now.Ticks / _timespan;
        }

        public int IndexOf (T item) {
            throw new NotImplementedException ();
        }

        public void Insert (int index, T item) {
            throw new NotImplementedException ();
        }

        public bool Remove (T item) {
            bool contains = false;
            foreach (List<T> list in _timeSpans.Values) {
                foreach (T t in list)
                    if (object.Equals (item, t)) {
                        contains = true;
                        break;
                    }
                if (contains) {
                    list.Remove (item);
                    return true;
                }
            }
            return false;
        }

        public void RemoveAt (int index) {
            List<T> list;
            int listIndex;
            GetListFromGlobalIndex (index, out list, out listIndex);
            list.RemoveAt (index);
        }



        // Private Methods (5) 

        private int GetLastIndex (long hourTimestamp) {
            return GetLastIndex (_timeSpans[hourTimestamp]);
        }

        private static int GetLastIndex (List<T> items) {
            int count = items.Count;
            if (count == 0)
                return 0;
            else
                return count - 1;
        }

        private List<T> GetList (long hourTimestamp) {
            List<T> items = _timeSpans[hourTimestamp];
            if (null == items)
                items = new List<T> (_itemCapacity);
            _timeSpans[hourTimestamp] = items;
            return items;
        }

        private void GetListFromGlobalIndex (int index, out List<T> list, out int listIndex) {
            list = null;
            listIndex = 0;
            int globalIndex = 0;
            int listsIndex = 0;
            IList<List<T>> lists = _timeSpans.Values;
            while (globalIndex < index) {
                list = lists[listsIndex];
                int listCount = list.Count;
                listIndex = 0;
                while (globalIndex < index && listIndex < listCount) {
                    globalIndex++;
                    listIndex++;
                }
            }
        }

        private void Shrink (long hourTimestamp) {
            if (_currentTimestamp == hourTimestamp)
                return;
            _currentTimestamp = hourTimestamp;
            if (_timeSpans.Count == _timespanCapacity)
                _timeSpans.RemoveAt (0);
        }


        #endregion Methods


        #region Explicit interface implementation
        void ICollection<T>.Add (T item) {
            this.Add (item);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return GetEnumerator ();
        }

        public T this[int index] {
            get {
                List<T> list;
                int listIndex;
                GetListFromGlobalIndex (index, out list, out listIndex);
                return list[listIndex];
            }
            set {
                List<T> list;
                int listIndex;
                GetListFromGlobalIndex (index, out list, out listIndex);
                list[listIndex] = value;
            }
        }

        int ICollection<T>.Count {
            get {
                long count = this.Count;
                if (count > int.MaxValue)
                    return int.MaxValue;
                else
                    return Convert.ToInt32 (count);
            }
        }
        #endregion
    }
}